package com.platform.gateway.filter;

import cn.hutool.core.collection.CollectionUtil;
import com.alibaba.fastjson.JSON;
import com.platform.common.core.constant.*;
import com.platform.common.core.domain.model.LoginUser;
import com.platform.common.core.utils.JwtUtils;
import com.platform.common.core.utils.ServletUtils;
import com.platform.common.core.utils.StringUtils;
import com.platform.common.redis.service.RedisService;
import com.platform.gateway.config.properties.IgnoreWhiteProperties;
import com.platform.gateway.constants.GatewayConstant;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.SignatureException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

import java.util.Arrays;
import java.util.List;

/**
 * 网关鉴权
 *
 * @author
 */
@Component
public class AuthFilter implements GlobalFilter, Ordered {
    private static final Logger log = LoggerFactory.getLogger(AuthFilter.class);

    // 排除过滤的 uri 地址，nacos自行添加
    @Autowired
    private IgnoreWhiteProperties ignoreWhite;

    @Autowired
    private RedisService redisService;

    // 是否允许账户多终端同时登录（true允许 false不允许）
    @Value("${token.soloLogin:false}")
    private boolean soloLogin;


    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        ServerHttpRequest request = exchange.getRequest();
        ServerHttpRequest.Builder mutate = request.mutate();

        String url = request.getURI().getPath();
        // 跳过不需要验证的路径
        if (StringUtils.matches(url, ignoreWhite.getWhites())) {
            return chain.filter(exchange);
        }
        String token = getToken(request);
        if (StringUtils.isEmpty(token)) {
            return unauthorizedResponse(exchange, "令牌不能为空");
        }
        Claims claims;
        try {
            claims = JwtUtils.parseToken(token);
        } catch (SignatureException e) {
            return unauthorizedResponse(exchange, "签名校验失败,可能被篡改！");
        }
        if (claims == null) {
            return unauthorizedResponse(exchange, "令牌已过期或验证不正确！");
        }
        if (isAppLimit(claims, request)) {
            return unauthorizedResponse(exchange, "外部客户身份权限受限,请联系管理员");
        }
        if (isTourist(claims, request)) {
            return unauthorizedResponse(exchange, "游客身份权限受限,请联系管理员");
        }

        // 外部应用token
        String appKey = JwtUtils.getAppKey(claims);
        if (StringUtils.isNotEmpty(appKey)) {
            boolean islogin = redisService.hasKey(CacheConstants.APP_TOKEN_KEY + token);
            if (!islogin) {
                return unauthorizedResponse(exchange, "登录状态已过期");
            }
            String appId = JwtUtils.getAppId(claims);
            String appName = JwtUtils.getAppName(claims);
            if (StringUtils.isEmpty(appId) || StringUtils.isEmpty(appName)) {
                return unauthorizedResponse(exchange, "令牌验证失败");
            }
            // 设置用户信息到请求
            addHeader(mutate, SecurityConstants.APP_KEY, appKey);
            addHeader(mutate, SecurityConstants.DETAILS_APP_ID, appId);
            addHeader(mutate, SecurityConstants.DETAILS_APP_NAME, appName);
        } else {
            // 系统token
            String userkey = JwtUtils.getUserKey(claims);
            boolean islogin = redisService.hasKey(getTokenKey(userkey));
            if (!islogin) {
                return unauthorizedResponse(exchange, "登录状态已过期");
            }
            String userid = JwtUtils.getUserId(claims);
            String username = JwtUtils.getUserName(claims);
            if (StringUtils.isEmpty(userid) || StringUtils.isEmpty(username)) {
                return unauthorizedResponse(exchange, "令牌验证失败");
            }

            //是否允许账户多终端同时登录（true允许 false不允许）
            if (!soloLogin) {
                LoginUser loginUser = redisService.getCacheObject(getTokenKey(userkey));
                String needCheck = loginUser.getVersion() == null ? "" : loginUser.getVersion();
                if (!StringUtils.isEmpty(loginUser.getFlag())) {
                    //获取登录用户最新版本号
                    Object cacheObject = redisService.getCacheObject(UserConstants.VERSION_PRE + userid + loginUser.getFlag());
                    String version = cacheObject == null ? "" : cacheObject.toString();
                    /*if (!version.equals(needCheck) && "pc".equals(loginUser.getFlag())){*/
                    if (!version.equals(needCheck)) {
                        redisService.deleteObject(getTokenKey(userkey));
                        return ServletUtils.webFluxResponseWriter(exchange.getResponse(), "您的账号已在别处登录！", HttpStatus.CONFLICT);
                    }
                }
            }

            // 设置用户信息到请求
            addHeader(mutate, SecurityConstants.USER_KEY, userkey);
            addHeader(mutate, SecurityConstants.DETAILS_USER_ID, userid);
            addHeader(mutate, SecurityConstants.DETAILS_USERNAME, username);
            // 内部请求来源参数清除
            removeHeader(mutate, SecurityConstants.FROM_SOURCE);
        }
        return chain.filter(exchange.mutate().request(mutate.build()).build());
    }

    private boolean isAppLimit(Claims claims, ServerHttpRequest request) {
        String appName = JwtUtils.getAppName(claims);
        if (StringUtils.isNotBlank(appName)) {
            String allApi = redisService.getCacheObject(CacheConstants.API_ALL_KEY);
            if (StringUtils.isEmpty(allApi)) {
                return true;
            }
            String[] allApis = allApi.split(",");
            List<String> allApiList = Arrays.asList(allApis);

            String uri = request.getURI().getPath();
            log.info("接口相对路径为:{}", uri);
            // 没有权限的数据受限
            return !StringUtils.matches(uri, allApiList);
        } else {
            return false;
        }
    }

    private boolean isTourist(Claims claims, ServerHttpRequest request) {
        String username = JwtUtils.getUserName(claims);
        String appName = JwtUtils.getAppName(claims);
	    if(StringUtils.isBlank(appName) && (StringUtils.isBlank(username) || username.contains("游客"))) {
            String uri = request.getURI().getPath();
            log.info("接口相对路径为:{}", uri);
            boolean matches=!StringUtils.matches( uri,GatewayConstant.GUEST_API_List );
           return matches;
	    }else {
            return false;
        }
    }

    private void addHeader(ServerHttpRequest.Builder mutate, String name, Object value) {
        if (value == null) {
            return;
        }
        String valueStr = value.toString();
        String valueEncode = ServletUtils.urlEncode(valueStr);
        mutate.header(name, valueEncode);
    }

    private void removeHeader(ServerHttpRequest.Builder mutate, String name) {
        mutate.headers(httpHeaders -> httpHeaders.remove(name)).build();
    }

    private Mono<Void> unauthorizedResponse(ServerWebExchange exchange, String msg) {
        log.error("[鉴权异常处理]请求路径:{}", exchange.getRequest().getPath());
        return ServletUtils.webFluxResponseWriter(exchange.getResponse(), msg, HttpStatus.UNAUTHORIZED);
    }

    /**
     * 获取缓存key
     */
    private String getTokenKey(String token) {
        return CacheConstants.LOGIN_TOKEN_KEY + token;
    }

    /**
     * 获取请求token
     */
    private String getToken(ServerHttpRequest request) {
        String token = request.getHeaders().getFirst(TokenConstants.AUTHENTICATION);
        // 如果前端设置了令牌前缀，则裁剪掉前缀
        if (StringUtils.isNotEmpty(token) && token.startsWith(TokenConstants.PREFIX)) {
            token = token.replaceFirst(TokenConstants.PREFIX, StringUtils.EMPTY);
        }
        return token;
    }

    @Override
    public int getOrder() {
        return -200;
    }
}
